
#include <JSL.hpp>

#include "../ShaderDefines.hpp"
#include "../../debug.hpp"
#include "../../gl.hpp"
#include "../../math.hpp"

#include "Mesh.hpp"

namespace zen {

	namespace video {

		namespace model {

			Mesh::Mesh() {
				init();
			}

			Mesh::Mesh(const Mesh &other) {
				init();
				*this = other;
			}

			Mesh::~Mesh() {
				free(false);
			}

			Mesh& Mesh::operator = (const Mesh &other) {
				free();
				indexCount = other.indexCount;
				indexes = new glm::u32vec3[indexCount];
				for (glm::uint i = 0; i < indexCount; i++)
					indexes[i] = other.indexes[i];
				vertexCount = other.vertexCount;
				vertexes = new glm::f32vec3[vertexCount];
				normals = new glm::f32vec3[vertexCount];
				texCoords = new glm::f32vec2[vertexCount];
				colors = new glm::u8vec4[vertexCount];
				for (glm::uint i = 0; i < vertexCount; i++) {
					indexes[i] = other.indexes[i];
					normals[i] = other.normals[i];
					texCoords[i] = other.texCoords[i];
					colors[i] = other.colors[i];
				}
				resultVertexes = vertexes;
				resultNormals = normals;
				resultTexCoords = texCoords;
				glGenVertexArrays(1, &vertexArray);
				if (vertexArray != 0) {
					glBindVertexArray(vertexArray);
					glGenBuffers(1, &indexBuffer);
					if (indexBuffer != 0) {
						glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
						glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexCount * getIndexSize(), indexes, GL_DYNAMIC_DRAW);
					}
					glGenBuffers(1, &vertexBuffer);
					if (vertexBuffer != 0) {
						glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
						glBufferData(GL_ARRAY_BUFFER, vertexCount * getVertexSize(), vertexes, GL_DYNAMIC_DRAW);
						glVertexAttribPointer(ZEN_SHADER_LOCATION_VERTEX_POSITION, 3, GL_FLOAT, GL_FALSE, 0, 0);
					}
					if (texCoords != nullptr) {
						glGenBuffers(1, &texCoordBuffer);
						if (texCoordBuffer != 0) {
							glBindBuffer(GL_ARRAY_BUFFER, texCoordBuffer);
							glBufferData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::f32vec2), texCoords, GL_DYNAMIC_DRAW);
							glVertexAttribPointer(ZEN_SHADER_LOCATION_VERTEX_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 0, 0);
						}
					}
					if (normals != nullptr) {
						glGenBuffers(1, &normalBuffer);
						if (normalBuffer != 0) {
							glBindBuffer(GL_ARRAY_BUFFER, normalBuffer);
							glBufferData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::f32vec3), normals, GL_DYNAMIC_DRAW);
							glVertexAttribPointer(ZEN_SHADER_LOCATION_VERTEX_NORMAL, 3, GL_FLOAT, GL_FALSE, 0, 0);
						}
					}
					if (colors != nullptr) {
						glGenBuffers(1, &colorBuffer);
						if (colorBuffer != 0) {
							glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
							glBufferData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::u8vec4), colors, GL_DYNAMIC_DRAW);
							glVertexAttribPointer(ZEN_SHADER_LOCATION_VERTEX_COLOR, 4, GL_UNSIGNED_BYTE, GL_FALSE, 0, 0);
						}
					}
					glBindBuffer(GL_ARRAY_BUFFER, 0);
					glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
					glBindVertexArray(0);
				}
				return *this;
			}

			inline void Mesh::init() {
				vertexArray = 0;
				indexBuffer = 0;
				vertexBuffer = 0;
				texCoordBuffer = 0;
				normalBuffer = 0;
				colorBuffer = 0;
				indexCount = 0;
				vertexCount = 0;
				indexes = NULL;
				vertexes = NULL;
				normals = NULL;
				texCoords = NULL;
				colors = NULL;
				resultVertexes = NULL;
				resultNormals = NULL;
				resultTexCoords = NULL;
			}

			inline void Mesh::free(bool reinitialize) {
				delete[] indexes;
				delete[] vertexes;
				delete[] normals;
				delete[] texCoords;
				delete[] colors;
				if (vertexes != resultVertexes)		delete[] resultVertexes;
				if (normals != resultNormals)		delete[] resultNormals;
				if (texCoords != resultTexCoords)	delete[] resultTexCoords;
				if (vertexArray != 0)		glDeleteVertexArrays(1, &vertexArray);
				if (indexBuffer != 0)		glDeleteBuffers(1, &indexBuffer);
				if (vertexBuffer != 0)		glDeleteBuffers(1, &vertexBuffer);
				if (texCoordBuffer != 0)	glDeleteBuffers(1, &texCoordBuffer);
				if (normalBuffer != 0)		glDeleteBuffers(1, &normalBuffer);
				if (colorBuffer != 0)		glDeleteBuffers(1, &colorBuffer);
				if (reinitialize) init();
			}

			void Mesh::update() {
				if (resultVertexes != vertexes)
					delete[] resultVertexes;
				resultVertexes = vertexes;
				if (resultNormals != normals)
					delete[] resultNormals;
				resultNormals = normals;
				if (resultTexCoords != texCoords)
					delete[] resultTexCoords;
				resultTexCoords = texCoords;

				// TODO: mesh transformation here...
			}

			void Mesh::draw() const {
				if (vertexArray == 0 || indexes == nullptr)
					return;
				glBindVertexArray(vertexArray);
				if (vertexBuffer != 0)
					glEnableVertexAttribArray(ZEN_SHADER_LOCATION_VERTEX_POSITION);
				else
					glDisableVertexAttribArray(ZEN_SHADER_LOCATION_VERTEX_POSITION);
				if (texCoordBuffer != 0)
					glEnableVertexAttribArray(ZEN_SHADER_LOCATION_VERTEX_TEXCOORD);
				else
					glDisableVertexAttribArray(ZEN_SHADER_LOCATION_VERTEX_TEXCOORD);
				if (normalBuffer != 0)
					glEnableVertexAttribArray(ZEN_SHADER_LOCATION_VERTEX_NORMAL);
				else
					glDisableVertexAttribArray(ZEN_SHADER_LOCATION_VERTEX_NORMAL);
				if (colorBuffer != 0)
					glEnableVertexAttribArray(ZEN_SHADER_LOCATION_VERTEX_COLOR);
				else
					glDisableVertexAttribArray(ZEN_SHADER_LOCATION_VERTEX_COLOR);
				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, indexBuffer);
				glDrawElements(GL_TRIANGLES, indexCount * 3, GL_UNSIGNED_INT, 0);
			}

			bool Mesh::loadResource(jsl::Node* node) {
				zen_call("Mesh.loadResource(jsl::Node*)");
				if (node == NULL) {
					zen_log("Error: Invalid resource, must not be null.");
					return false;
				}
		
				jsl::Node* tmp;

				free();
				glGenVertexArrays(1, &this->vertexArray);
				glBindVertexArray(this->vertexArray);

				try {
					/// indexes
					tmp = node->at("indexes");
					indexCount = (glm::uint)(tmp->size() / 3);
					if (indexCount > 0 && indexCount * 3 == tmp->size()) {
            			indexes = new glm::u32vec3[indexCount];
						for (jsl::JSLarraySize i = 0; i < tmp->size(); i++)
							((glm::u32*)indexes)[i] = (glm::u32)tmp->at(i)->asInt();
						if (this->vertexArray != 0) {
							glGenBuffers(1, &this->indexBuffer);
							if (this->indexBuffer != 0) {
								glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->indexBuffer);
								glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->indexCount * sizeof(glm::u32vec3), this->indexes, GL_DYNAMIC_DRAW);
							}
						}
					} else {
						delete indexes;
						indexes = NULL;
						zen_log("Error: No indexes (and they're not optional).");
						return false;
					}

					/// vertexes
					tmp = node->at("vertexes");
					vertexCount = (glm::uint)(tmp->size() / 3);
					if (vertexCount > 0 && vertexCount * 3 == tmp->size()) {
            			vertexes = new glm::f32vec3[vertexCount];
						for (jsl::JSLarraySize i = 0; i < tmp->size(); i++)
							((glm::f32*)vertexes)[i] = (glm::f32)tmp->at(i)->asFloat();
						if (this->vertexArray != 0) {
							glBindVertexArray(this->vertexArray);
							glGenBuffers(1, &this->vertexBuffer);
							if (this->vertexBuffer != 0) {
								glBindBuffer(GL_ARRAY_BUFFER, this->vertexBuffer);
								glBufferData(GL_ARRAY_BUFFER, this->vertexCount * sizeof(glm::f32vec3), this->vertexes, GL_DYNAMIC_DRAW);
								glVertexAttribPointer(ZEN_SHADER_LOCATION_VERTEX_POSITION, 3, GL_FLOAT, GL_FALSE, 0, 0);
							}
						}
					} else {
						delete vertexes;
						vertexes = NULL;
						zen_log("Error: No vertexes (and they're not optional).");
						return false;
					}

					/// texCoords
					tmp = node->at("texCoords");
					glm::uint texCoordCount = (glm::uint)(tmp->size() / 2);
					if (texCoordCount == vertexCount) {
						texCoords = new glm::f32vec2[texCoordCount];
						for (jsl::JSLarraySize i = 0; i < tmp->size(); i++)
							((glm::f32*)texCoords)[i] = (glm::f32)tmp->at(i)->asFloat();
						if (this->vertexArray != 0) {
							glBindVertexArray(this->vertexArray);
							glGenBuffers(1, &this->texCoordBuffer);
							if (this->texCoordBuffer != 0) {
								glBindBuffer(GL_ARRAY_BUFFER, this->texCoordBuffer);
								glBufferData(GL_ARRAY_BUFFER, this->vertexCount * sizeof(glm::f32vec2), this->texCoords, GL_DYNAMIC_DRAW);
								glVertexAttribPointer(ZEN_SHADER_LOCATION_VERTEX_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 0, 0);
							}
						}
					} else {
						delete texCoords;
						texCoords = NULL;
					}

					/// normals
					tmp = node->at("normals");
					glm::uint normalCount = (glm::uint)(tmp->size() / 3);
					if (normalCount == vertexCount) {
						normals = new glm::f32vec3[normalCount];
						for (jsl::JSLarraySize i = 0; i < tmp->size(); i++)
							((glm::f32*)normals)[i] = (glm::f32)tmp->at(i)->asFloat();
						if (this->vertexArray != 0) {
							glBindVertexArray(this->vertexArray);
							glGenBuffers(1, &this->normalBuffer);
							if (this->normalBuffer != 0) {
								glBindBuffer(GL_ARRAY_BUFFER, this->normalBuffer);
								glBufferData(GL_ARRAY_BUFFER, this->vertexCount * sizeof(glm::f32vec3), this->normals, GL_DYNAMIC_DRAW);
								glVertexAttribPointer(ZEN_SHADER_LOCATION_VERTEX_NORMAL, 3, GL_FLOAT, GL_FALSE, 0, 0);
							}
						}
					} else {
						delete normals;
						normals = NULL;
					}

					/// colors
					tmp = node->at("colors");
					glm::uint  colorCount = (glm::uint)(tmp->size() / 4);
					if (colorCount == vertexCount) {
						colors = new glm::u8vec4[colorCount];
						for (jsl::JSLarraySize i = 0; i < tmp->size(); i++)
							((glm::u8*)colors)[i] = (glm::u8)tmp->at(i)->asInt();
						if (this->vertexArray != 0) {
							glBindVertexArray(this->vertexArray);
							glGenBuffers(1, &this->colorBuffer);
							if (this->colorBuffer != 0) {
								glBindBuffer(GL_ARRAY_BUFFER, this->colorBuffer);
								glBufferData(GL_ARRAY_BUFFER, this->vertexCount * sizeof(glm::u8vec4), this->colors, GL_DYNAMIC_DRAW);
								glVertexAttribPointer(ZEN_SHADER_LOCATION_VERTEX_COLOR, 4, GL_UNSIGNED_BYTE, GL_FALSE, 0, 0);
							}
						}
					} else {
						delete colors;
						colors = NULL;
					}

					resultVertexes = vertexes;
					resultNormals = normals;
					resultTexCoords = texCoords;

				} catch (const std::exception &e) {
					zen_log("Error: " + std::string(e.what()));
					return false;
				}

				glBindBuffer(GL_ARRAY_BUFFER, 0);
				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
				glBindVertexArray(0);
				return true;
			}
		
		} // namespace model

	} // namespace video

} // namespace zen
